use base::{err, err_value, throw_err, ECode, Resp};
use serde_json::json;
use std::env;
use tracing::{instrument, warn};
use url::Url;

pub struct Client {
    client: reqwest::Client,
}

impl Client {
    pub async fn field(&self, tab: &str, db: &str) -> Resp {
        let n: Vec<String> = tab
            .split(",")
            .map(|t| format!(r#""{}""#, t.trim()))
            .collect();
        let n = n.join(",");

        let cmd = format!(r#"show field keys from {}"#, n);
        self.send_query(&cmd, db).await
    }

    pub async fn tag(&self, tab: &str, db: &str) -> Resp {
        let n: Vec<String> = tab
            .split(",")
            .map(|t| format!(r#""{}""#, t.trim()))
            .collect();
        let n = n.join(",");

        let cmd = format!(r#"show tag keys from {}"#, n);
        self.send_query(&cmd, db).await
    }

    pub async fn table(&self, db: &str) -> Resp {
        let cmd = "show measurements";
        self.send_query(cmd, db).await
    }

    pub async fn drop_table(&self, tab: &str, db: &str) -> Resp {
        let cmd = format!(r#"drop measurement "{}""#, tab);
        self.send_query(&cmd, db).await
    }

    pub async fn rm_row(&self, tab: &str, op: &str, val: &str, db: &str) -> Resp {
        let cmd = format!(r#"delete from "{}" where time {} '{}'"#, tab, op, val);
        self.send_query(&cmd, db).await
    }

    #[instrument(skip_all, name = "db")]
    pub async fn send_query(&self, q: &str, db: &str) -> Resp {
        let query = self.get_query(db);

        warn!("query: {:?} {:?}", query, q);

        let url = match Url::parse_with_params(&query, &[("q", q)]) {
            Ok(v) => v,
            Err(e) => throw_err!(ECode::Post, e),
        };

        let body = match self.client.post(url.as_str()).send().await {
            Ok(v) => v,
            Err(e) => throw_err!(ECode::PostJson, e),
        };

        let body = match body.text().await {
            Ok(v) => v,
            Err(e) => throw_err!(ECode::PostJson, e),
        };

        serde_json::from_str(&body).or_else(|e| err!(ECode::PostJson, e))
    }

    #[instrument(skip_all, name = "db_tm")]
    pub async fn send_query_tm(&self, q: &str, db: &str) -> Resp {
        let querytm = self.get_query_tm(db);

        warn!("query: {:?} {:?}", querytm, q);

        let url = match Url::parse_with_params(&querytm, &[("q", q)]) {
            Ok(v) => v,
            Err(e) => throw_err!(ECode::Post, e),
        };

        let body = match self.client.post(url.as_str()).send().await {
            Ok(v) => v,
            Err(e) => throw_err!(ECode::PostJson, e),
        };

        let body = match body.text().await {
            Ok(v) => v,
            Err(e) => throw_err!(ECode::PostJson, e),
        };

        serde_json::from_str(&body).or_else(|e| err!(ECode::PostJson, e))
    }

    #[instrument(skip_all, name = "db_w")]
    pub async fn send_write(&self, data: &str, db: &str) -> Resp {
        let write = self.get_write(db);

        warn!("write: {:?} {:?}", write, data);

        let body = match self.client.post(write).body(data.to_string()).send().await {
            Ok(v) => v,
            Err(e) => throw_err!(ECode::PostJson, e),
        };

        match body.status().as_u16() {
            204 => Ok(json!("Success")),
            _ => throw_err!(ECode::PostJson, body),
        }
    }

    fn get_query(&self, db: &str) -> String {
        if cfg!(windows) {
            format!("http://127.0.0.1:8086/query?u={db}&p={db}&db={db}")
        } else {
            match env::var("RUN_IN_LOCAL") {
                Ok(_) => format!("http://localhost:8086/query?u={db}&p={db}&db={db}"),
                Err(_) => format!("http://dockerhost:8086/query?u={db}&p={db}&db={db}"),
            }
        }
    }

    fn get_query_tm(&self, db: &str) -> String {
        if cfg!(windows) {
            format!("http://127.0.0.1:8086/query?u={db}&p={db}&db={db}&epoch=ns")
        } else {
            match env::var("RUN_IN_LOCAL") {
                Ok(_) => format!("http://localhost:8086/query?u={db}&p={db}&db={db}&epoch=ns"),
                Err(_) => format!("http://dockerhost:8086/query?u={db}&p={db}&db={db}&epoch=ns"),
            }
        }
    }

    fn get_write(&self, db: &str) -> String {
        if cfg!(windows) {
            format!("http://127.0.0.1:8086/write?u={db}&p={db}&db={db}")
        } else {
            match env::var("RUN_IN_LOCAL") {
                Ok(_) => format!("http://localhost:8086/write?u={db}&p={db}&db={db}"),
                Err(_) => format!("http://dockerhost:8086/write?u={db}&p={db}&db={db}"),
            }
        }
    }

    pub fn new() -> Self {
        Client {
            client: reqwest::Client::new(),
        }
    }
}
